﻿using System;
using System.Collections.Generic;
using SdmxMl.Common;
using SdmxMl.Structure;
using SdmxMl.GenericMetadata;

namespace SdmxMl.Manager
{
    public class ReferenceUsage
    {
        private SmManager sm;
        private ArtefactRefTyped arTarget;
        private Dictionary<string, ReferenceItem> refList;
        private string conceptId;
        UrnParser upTarget;
        private bool schemeOnly;

        private List<RefForMetaset> ListRefForMetaset;

        public ReferenceUsage(SmManager smMgr, UrnParser up)
        {
            ArtefactRefTyped a = new ArtefactRefTyped(up.ArType, up.FullIdent);
            if (smMgr == null)
                throw new ArgumentNullException("SmMgr", "Artefact container required");

            if (a.Type != SdmxArtefactType.CodeLists && a.Type != SdmxArtefactType.Concepts && a.Type != SdmxArtefactType.Dataflows)
                throw new ArgumentException("Unsupported artefact type received in usage reference");

            // Looking for precise concept or only schema
            schemeOnly = a.Type == SdmxArtefactType.Concepts  && string.IsNullOrEmpty(up.SubType);

            upTarget = up;
            arTarget = a;
            sm = smMgr;
            ListRefForMetaset = new List<RefForMetaset>();

            if (a.Type == SdmxArtefactType.CodeLists)
                GetItemsReferingCodelist();
            else if (a.Type == SdmxArtefactType.Concepts)
            {
                conceptId = up.SubTypeId;
                GetItemsReferingConcept();
            }
            else if (a.Type == SdmxArtefactType.Dataflows)
            {
                GetItemsReferingFlows();
            }
        }

        public Dictionary<string, ReferenceItem> GetMemoryReferences()
        {
            return refList;
        }

        private void GetItemsReferingFlows()
        {
            ArtefactManager arm = null;
            string fullId = arTarget.FullIdent;
            refList = new Dictionary<string, ReferenceItem>(16);

            // look in categoryScheme
            arm = sm.GetArtefactManager(SdmxArtefactType.Concepts);
            foreach (ConceptSchemeType cst in arm.ArtefactList)
            {
                // Concept can refers codelists in Corerepresentation
                foreach (ConceptType c in cst.ConceptList)
                {
                    if (c.CoreRepresentation.IsFilled)
                    {
                        if (c.CoreRepresentation.FullIdent == arTarget.FullIdent)
                        {
                            ReferenceItem ri = new ReferenceItem(cst, c);
                            if (refList.ContainsKey(ri.Urn) == false)
                                refList.Add(ri.Urn, ri);
                        }
                    }
                }
            }
        }


        private void GetItemsReferingCodelist()
        {
            ArtefactManager arm = null;
            string fullId = arTarget.FullIdent;
            refList = new Dictionary<string, ReferenceItem>(16);

            // look in conceptScheme
            arm = sm.GetArtefactManager(SdmxArtefactType.Concepts);
            foreach (ConceptSchemeType cst in arm.ArtefactList)
            {
                // Concept can refers codelists in Corerepresentation
                foreach (ConceptType c in cst.ConceptList)
                {
                    if (c.CoreRepresentation.IsFilled)
                    {
                        if (c.CoreRepresentation.FullIdent == arTarget.FullIdent)
                        {
                            ReferenceItem ri = new ReferenceItem(cst, c);
                            if (refList.ContainsKey(ri.Urn) == false)
                                refList.Add(ri.Urn, ri);
                        }
                    }
                }
            }

            // Look in HierarchicalCodelists
            arm = sm.GetArtefactManager(SdmxArtefactType.HierarchicalCodelists);
            foreach (HierarchicalCodelistType hclt in arm.ArtefactList)
            {
                foreach (CodelistRefType clrt in hclt.CodelistRefList)
                {
                    if (clrt.IsFilled)
                    {
                        if (clrt.FullIdent == arTarget.FullIdent)
                        {
                            ReferenceItem ri = new ReferenceItem(hclt, clrt);
                            if (refList.ContainsKey(ri.Urn) == false)
                                refList.Add(ri.Urn, ri);
                        }
                    }
                        
                }
            }


            // look in DSD
            arm = sm.GetArtefactManager(SdmxArtefactType.KeyFamilies);
            foreach (KeyFamilyType kft in arm.ArtefactList)
            {
                // Dimensions
                IterateDsdList(kft, kft.DimensionList);

                // Cross sectional measures
                IterateDsdList(kft, kft.CrossSectionalMeasureList);

                // Attributes
                IterateDsdList(kft, kft.AttributeList);
            }

            // look in MSD
            arm = sm.GetArtefactManager(SdmxArtefactType.MetadataStructureDefinitions);
            foreach (MetadataStructureDefinitionType msd in arm.ArtefactList)
            {
                // Reports
                foreach (ReportStructureType rpt in msd.ReportStructureList)
                    IterateHierarchyMetaAttrib(msd, rpt,rpt.MetadataAttributeList);
            }

            // Look in generic Metadataset
            arm = sm.GetArtefactManager(SdmxArtefactType.MetadataSet);
            foreach (MetadataSetType mdst in arm.ArtefactList)
            {
                foreach (RefForMetaset rfMeta in ListRefForMetaset)
                {
                    if (rfMeta.MsdFullId == mdst.MetadataStructureRef.FullIdent && mdst.ReportRef == rfMeta.ReportId)
                    {
                        // For each meta row
                        foreach (AttributeValueSetType at in mdst.AttributeValueSetList)
                        {
                            // Get URI value
                            ReportedAttributeType rptAtt = at.ReportedAttributeList.GetReportedAttributeOfConcept(rfMeta.ConceptId);
                            if (rptAtt.Value.GetFirst() == upTarget.SubmittedUrn)
                            {
                                ReferenceItem ri = new ReferenceItem(mdst, at, rfMeta);
                                if (refList.ContainsKey(ri.Urn) == false)
                                    refList.Add(ri.Urn, ri);
                            }
                        }
                    }
                }
            }
        }

        private void IterateHierarchyMetaAttrib(MetadataStructureDefinitionType msd, ReportStructureType rpt, MetadataAttributeTypeList metaList)
        {
            foreach (MetadataAttributeType meta in metaList)
            {
                if (meta.RepresentationInfo.IsFilled)
                {
                    if (meta.RepresentationInfo.FullIdent == arTarget.FullIdent)
                    {
                        ReferenceItem ri = new ReferenceItem(msd, rpt, meta);
                        if (refList.ContainsKey(ri.Urn) == false)
                            refList.Add(ri.Urn, ri);
                    }
                }

                // record case using URI in metadataset, for next domain search
                if (meta.TextFormat.TextType == TextTypeType.URI && meta.ConceptSchemeInfo.IsFilled && meta.ConceptInfo.Id.Length > 0)
                   ListRefForMetaset.Add (new RefForMetaset(msd, rpt, meta));


                IterateHierarchyMetaAttrib(msd, rpt, meta.MetadataAttributeList);
            }
       }

        private void IterateHierarchyMetaAttribForConcept(MetadataStructureDefinitionType msd, ReportStructureType rpt, MetadataAttributeTypeList metaList)
        {
            foreach (MetadataAttributeType meta in metaList)
            {
                if (meta.ConceptSchemeInfo.IsFilled)
                {
                    if (meta.ConceptSchemeInfo.FullIdent == arTarget.FullIdent &&
                        meta.ConceptInfo.Id == conceptId)
                    {
                        ReferenceItem ri = new ReferenceItem(msd, rpt, meta);
                        if (refList.ContainsKey(ri.Urn) == false)
                            refList.Add(ri.Urn, ri);
                    }

                    // record case using URI in metadataset, for next domain search
                    if (meta.TextFormat.TextType == TextTypeType.URI && meta.ConceptInfo.Id.Length > 0)
                       ListRefForMetaset.Add (new RefForMetaset(msd, rpt, meta));
                }

                IterateHierarchyMetaAttribForConcept(msd, rpt, meta.MetadataAttributeList);
            }
        }

        private void IterateDsdList(KeyFamilyType kft, BaseDimensionTypeList bdtl)
        {
            foreach (BaseDimensionType bdt in bdtl)
            {
                if (bdt != null && bdt.CodelistInfo.IsFilled)
                {
                    if (bdt.CodelistInfo.FullIdent == arTarget.FullIdent)
                    {
                        ReferenceItem ri = new ReferenceItem(kft, bdt);
                        if (refList.ContainsKey(ri.Urn) == false)
                            refList.Add(ri.Urn, ri);
                    }
                }
            }
        }

        private void IterateDsdListForConcept(KeyFamilyType kft, BaseDimensionTypeList bdtl)
        {
            foreach (BaseDimensionType bdt in bdtl)
            {
                if (bdt != null && bdt.ConceptSchemeInfo.IsFilled)
                {
                    if (bdt.ConceptSchemeInfo.FullIdent == arTarget.FullIdent && (schemeOnly ||
                        bdt.ConceptInfo.Id == conceptId))
                    {
                        ReferenceItem ri = new ReferenceItem(kft, bdt);
                        if (refList.ContainsKey(ri.Urn) == false)
                            refList.Add(ri.Urn, ri);
                    }
                }
            }
        }

        private void GetItemsReferingConcept()
        {
            ArtefactManager arm = null;
            string fullId = arTarget.FullIdent;
            refList = new Dictionary<string, ReferenceItem>(16);

            // look in DSD
            arm = sm.GetArtefactManager(SdmxArtefactType.KeyFamilies);
            foreach (KeyFamilyType kft in arm.ArtefactList)
            {
                // Dimensions
                IterateDsdListForConcept(kft, kft.DimensionList);

                // Cross sectional measures
                IterateDsdListForConcept(kft, kft.CrossSectionalMeasureList);

                // Attributes
                IterateDsdListForConcept(kft, kft.AttributeList);
            }

            // look in MSD
            arm = sm.GetArtefactManager(SdmxArtefactType.MetadataStructureDefinitions);
            foreach (MetadataStructureDefinitionType msd in arm.ArtefactList)
            {
                // Reports
                foreach (ReportStructureType rpt in msd.ReportStructureList)
                    IterateHierarchyMetaAttribForConcept(msd, rpt, rpt.MetadataAttributeList);
            }

            // Look in generic Metadataset
            arm = sm.GetArtefactManager(SdmxArtefactType.MetadataSet);
            foreach (MetadataSetType mdst in arm.ArtefactList)
            {
                foreach (RefForMetaset rfMeta in ListRefForMetaset)
                {
                    if (rfMeta.MsdFullId == mdst.MetadataStructureRef.FullIdent && mdst.ReportRef == rfMeta.ReportId)
                    {
                        // For each meta row
                        foreach (AttributeValueSetType at in mdst.AttributeValueSetList)
                        {
                            // Get URI value
                            ReportedAttributeType rptAtt = at.ReportedAttributeList.GetReportedAttributeOfConcept(rfMeta.ConceptId);
                            if (rptAtt.Value.GetFirst() == upTarget.SubmittedUrn)
                            {
                                ReferenceItem ri = new ReferenceItem(mdst, at, rfMeta);
                                if (refList.ContainsKey(ri.Urn) == false)
                                    refList.Add(ri.Urn, ri);

                            }
                        }
                    }
                }
            }

        }

    }
    /// <summary>
    /// Helper class for construction of table of found references
    /// </summary>
    public class ReferenceItem
    {
        public bool Selected { get; set; }
        public string RefType { get; set; }
        public string Id { get; set; }
        public string Name { get; set; }
        public string Urn { get; set; }

        public ReferenceItem(ConceptSchemeType cst, ConceptType c)
        {
            RefType = "Concept";
            Id = c.Id;
            Name = c.Name.GetFirst();
            Urn = UrnManager.FullIdToUrn(SdmxArtefactType.Concepts.ToString(), cst.FullIdent, c, null);
        }

        public ReferenceItem(HierarchicalCodelistType hclt, CodelistRefType c)
        {
            RefType = "HierarchicalCodelist";
            Id = c.Id;
            Name = c.Alias;
            Urn = UrnManager.FullIdToUrn(SdmxArtefactType.HierarchicalCodelists.ToString(), hclt.FullIdent, c, null);
        }

        public ReferenceItem(KeyFamilyType kft, BaseDimensionType bdt)
        {
            RefType = "DSD";
            Id = bdt.ConceptInfo.Id;
            Name = kft.Name.GetFirst();
            Urn = UrnManager.FullIdToUrn(SdmxArtefactType.KeyFamilies.ToString(), kft.FullIdent, bdt, null);
        }

        public ReferenceItem(MetadataStructureDefinitionType msd, ReportStructureType rpt, MetadataAttributeType meta)
        {
            RefType = "MSD";
            Id = meta.ConceptInfo.Id;
            Name = msd.Name.GetFirst();
            Urn = UrnManager.FullIdToUrn(SdmxArtefactType.MetadataStructureDefinitions.ToString(), msd.FullIdent, meta, rpt);
        }

        public ReferenceItem(MetadataSetType mdst, AttributeValueSetType at, RefForMetaset refMetaSet)
        {
            RefType = "MetadataSet";
            Id =at.ToString();
            Name = mdst.Name.GetFirst();
            Urn = UrnManager.FullIdToUrn(SdmxArtefactType.MetadataSet.ToString(), mdst.FullIdent, at, refMetaSet);
        } 
    }

    /// <summary>
    /// Helper class to remember Metadataset references for URI reported attributes
    /// </summary>
    public class RefForMetaset
    {
        public string MsdFullId { get; set; }
        public string ReportId { get; set; }
        public string ConceptSchemeFullId { get; set; }
        public string ConceptId { get; set; }

        public RefForMetaset(MetadataStructureDefinitionType msd, ReportStructureType rpt, MetadataAttributeType meta)
        {
            MsdFullId = msd.FullIdent;
            ReportId = rpt.Id;
            ConceptSchemeFullId = meta.ConceptSchemeInfo.FullIdent;
            ConceptId = meta.ConceptInfo.Id;
        }
    }
}
